import pygame
import moderngl
import numpy as np
from math import pi, sin, cos, tau
from pyrr import Matrix44, Quaternion, Vector3

def V3(x, y, z):
    return Vector3([x, y, z], dtype="f4")

def VD3(x, y, z):
    return Vector3([x, y, z])

def U3(x, y, z):
    return Vector3([x, z, -y], dtype="f4")

def UD3(x, y, z):
    return Vector3([x, z, -y])

def norm(v):
    return np.linalg.norm(v)

def ev(v):
    return v / norm(v)

U3.fd = U3(0, 1, 0)
U3.bk = U3(0, -1, 0)
U3.rt = U3(1, 0, 0)
U3.lt = U3(-1, 0, 0)
U3.up = U3(0, 0, 1)
U3.dn = U3(0, 0, -1)
U3.c1 = U3(1, 1, 1)
U3.c2 = U3(-1, 1, 1)
U3.c3 = U3(-1, -1, 1)
U3.c4 = U3(1, -1, 1)
U3.c5 = U3(1, 1, -1)
U3.c6 = U3(-1, 1, -1)
U3.c7 = U3(-1, -1, -1)
U3.c8 = U3(1, -1, -1)

vrot = Quaternion.from_axis_rotation

class Camera:
    def __init__(self, pos=None):
        # ~ self.proj = Matrix44.perspective_projection(20.0, 10 / 8, 0.1, 1000.0)
        self.proj = Matrix44.perspective_projection(60.0, 10 / 8, 0.1, 500.0)
        self.rot = Quaternion()
        if pos is None:
            self.pos = V3(0, 1, 20)
        else:
            self.pos = pos


    def right(self):
        return self.rot * V3(1, 0, 0)

    def up(self):
        return self.rot * V3(0, 1, 0)

    def forward(self):
        return self.rot * V3(0, 0, -1)

    def p(self):
        return self.proj.astype("f4")

    def v(self):
        return (self.rot.matrix44 *  Matrix44.from_translation(-self.pos)).astype('f4')

    # ~ def mvp(self):
        # ~ return (self.proj * self.rot.matrix44 *  Matrix44.from_translation(-self.pos)).astype('f4')

prog1 = {
            "vertex_shader":'''
#version 330 core
in vec3 aPos;
in vec3 aNormal;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
//uniform mat4 normalMat;

  out vec4 vPos;
  out vec3 wNormal;     // world
  out vec3 vNormal;         // view

void main() {
    vPos = modelMat * vec4(aPos, 1.0);
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    //vNormal = mat3(transpose(inverse(modelMat))) * aNormal;
    //vNormal = mat3(normalMat) * aNormal;
    gl_Position = projMat * viewMat * vPos;
  }
            ''',
            "fragment_shader":'''
#version 330 core
out vec4 FragColor;

uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 objectColor;
uniform int get_normal;

in vec4 vPos;
in vec3 wNormal;
in vec3 vNormal;


void main()
{
    if (get_normal == 3){
        vec3 norm = normalize(vNormal);
        //FragColor = vec4(norm / 2.0 + vec3(0.5, 0.5, 0.5), 1.0);
        FragColor = vec4(norm / 2.0 + vec3(0.5, 0.5, 0.5), gl_FragCoord.z);
        return;
    }



    vec3 FragPos = vPos.xyz;
    float ambientStrength = 0.05;
    vec3 ambient = ambientStrength * objectColor;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir) , 0.0);
    //vec3 diffuse = diff * vec3(0.5, 0.5, 0.5) ;
    vec3 diffuse = diff * objectColor ;

    vec3 viewDir = normalize(viewPos - FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);

    //vec3 red = vec3(1, 0, 0);

    FragColor = vec4(result, 1.0);

}
            '''}


prog2 = {
            "vertex_shader":'''
#version 330 core
in vec3 aPos;
in vec3 aNormal;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
//uniform mat4 normalMat;

  out vec4 vPos;
  out vec3 wNormal;     // world
  out vec3 vNormal;

void main() {
    vPos = modelMat * vec4(aPos, 1.0);
    wNormal = mat3( modelMat) * aNormal;
    vNormal = mat3( viewMat * modelMat) * aNormal;
    //vNormal = mat3(transpose(inverse(modelMat))) * aNormal;
    //vNormal = mat3(normalMat) * aNormal;
    gl_Position = projMat * viewMat * vPos;
  }
            ''',
            "fragment_shader":'''
#version 330 core
out vec4 FragColor;

uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 objectColor;
uniform int get_normal;

in vec4 vPos;
in vec3 wNormal;
in vec3 vNormal;


void main()
{
    if (get_normal == 3){
        vec3 norm = normalize(vNormal);
        //FragColor = vec4(norm / 2.0 + vec3(0.5, 0.5, 0.5), 1.0);
        FragColor = vec4(norm / 2.0 + vec3(0.5, 0.5, 0.5), gl_FragCoord.z);
        return;
    }


    vec3 FragPos = vPos.xyz;
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * objectColor;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir) , 0.0);
    //vec3 diffuse = diff * vec3(0.5, 0.5, 0.5) ;
    vec3 diffuse = diff * objectColor ;
    diffuse = diffuse * 0.3 + 0.7;


    vec3 viewDir = normalize(viewPos - FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);

    //vec3 red = vec3(1, 0, 0);

    FragColor = vec4(result, 1.0);
    //FragColor = vec4(result * 0 + objectColor, 1.0);
}
            '''}

progt1 = {
            "vertex_shader":'''
#version 330 core
in vec3 aPos;
in vec3 aNormal;
in vec2 aPos_t;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
//uniform mat4 normalMat;

  out vec4 wPos;
  out vec3 wNormal;     // world
  out vec3 vNormal;         // view
  out vec2 Pos_t;
void main() {
    wPos = modelMat * vec4(aPos, 1.0);
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    //vNormal = mat3(transpose(inverse(modelMat))) * aNormal;
    //vNormal = mat3(normalMat) * aNormal;
    gl_Position = projMat * viewMat * wPos;
    Pos_t = aPos_t;
  }
            ''',
            "fragment_shader":'''
#version 330 core
out vec4 FragColor;

uniform vec3 lightPos;
uniform vec3 viewPos;
uniform int get_normal;
uniform sampler2D texture;

in vec4 wPos;
in vec3 wNormal;
in vec3 vNormal;
in vec2 Pos_t;

void main()
{
    if (get_normal == 3){
        vec3 norm = normalize(vNormal);
        //FragColor = vec4(norm / 2.0 + vec3(0.5, 0.5, 0.5), 1.0);
        FragColor = vec4(norm / 2.0 + vec3(0.5, 0.5, 0.5), gl_FragCoord.z);
        return;
    }


    vec4 color = texture2D(texture, vec2(Pos_t.x, -Pos_t.y));

    vec3 FragPos = wPos.xyz;
    float ambientStrength = 0.05;
    vec4 ambient = ambientStrength * color;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir) , 0.0);
    //vec3 diffuse = diff * vec3(0.5, 0.5, 0.5) ;
    vec4 diffuse = diff * color ;

    vec3 viewDir = normalize(viewPos - FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec4 specular = spec * vec4(0.3, 0.3, 0.3, 1);
    
    
    vec4 result = ambient + diffuse + specular * 0.1;

    
    FragColor = result;

}
            '''}

class Drawable:
    pass

class Moveable:
    pass

class Obj(Drawable, Moveable):
    def __init__(self):
        self.prog = prog1
        self.vertices = []
        self.normals = []
        self.mode = moderngl.TRIANGLES
        self.t = Turtle()
        self.box = None
        self.color = V3(0.5, 0.5, 0.5)

        self.vao = None

    def u(self, ctx):
        assert len(self.vertices) != 0
        assert isinstance(ctx, moderngl.Context)
        self.prog = ctx.program(**self.prog)
        self.vao= self.get_vao(ctx)


    def copy(self):
        o = Obj()
        o.prog = self.prog
        o.vertices = self.vertices.copy()
        o.normals = self.normals.copy()
        o.t = self.t.copy()
        if self.box is not None:
            o.box = self.box.copy(o)
        o.vao = self.vao
        o.color = self.color
        return o

    def as_unit(self):
        v = np.array(self.vertices) @ np.array(self.t.abs().rot.matrix33).T + self.t.abs().pos
        n = np.array(self.normals) @ np.array(self.t.abs().rot.matrix33).T
        return v, n

    def __rrshift__(self, obj):
        if len(obj) != 2:
            raise Exception(f"add part faild, {obj}")
        else:
            v, n = obj
            assert len(v) == len(n)
            self.vertices.extend(v)
            self.normals.extend(n)

    def m(self):
        t = self.t.abs()
        return ( Matrix44.from_translation(t.pos) * t.rot.matrix44.T).astype("f4")


    def get_vao(self, ctx):
        vertices = np.array(self.vertices, dtype="f4")
        normals = np.array(self.normals, dtype="f4")
        # ~ print("::", vertices.shape)
        # ~ print("::", normals.shape)
        a, b, c = vertices[:,0], vertices[:,1], vertices[:,2]
        d, e, f = normals[:,0], normals[:,1], normals[:,2]
        x = np.dstack([a, b, c, d, e, f ])
        vbo = ctx.buffer(x.astype("f4"))
        return ctx.simple_vertex_array(self.prog, vbo, 'aPos', 'aNormal')

    def draw(self, camera):
        if self.vao is None:
            print("没准备好绘制", self)
            return

        self.prog['modelMat'].write(self.m())
        self.prog['viewMat'].write(camera.v())
        self.prog['projMat'].write(camera.p())
        self.prog['lightPos'].write(camera.pos)
        self.prog['viewPos'].write(camera.pos)
        self.prog['objectColor'].write(self.color)
        self.prog['get_normal'].value = 0
        self.vao.render(self.mode)

    def draw_normal(self, camera):
        if self.vao is None:
            print("没准备好绘制", self)
            return

        self.prog['modelMat'].write(self.m())
        self.prog['viewMat'].write(camera.v())
        self.prog['projMat'].write(camera.p())
        self.prog['lightPos'].write(camera.pos)
        self.prog['viewPos'].write(camera.pos)
        self.prog['objectColor'].write(self.color)
        self.prog['get_normal'].value = 3
        self.vao.render(self.mode)


    def pile(self, t, anchor:"U(-1~1, -1~1, -1~1)"):
        a = anchor * self.box.size / 2
        t = t.abs()
        self.t.pos = t.pos - t.rot * a
        self.t.rot= t.rot
        
class Objt(Drawable, Moveable):
    def __init__(self, tex):
        self.prog = progt1
        self.vertices = []
        self.normals = []
        self.vertices_t = []
        self.mode = moderngl.TRIANGLES
        self.t = Turtle()
        self.box = None
        self.color = V3(0.5, 0.5, 0.5)
        self.tex = pygame.image.load(tex)
        
        

        self.vao = None

    def u(self, ctx):
        assert len(self.vertices) != 0
        assert isinstance(ctx, moderngl.Context)
        self.prog = ctx.program(**self.prog)
        self.vao= self.get_vao(ctx)


    def copy(self):
        o = Obj()
        o.prog = self.prog
        o.vertices = self.vertices.copy()
        o.normals = self.normals.copy()
        o.vertices_t = self.vertices_t.copy()
        o.t = self.t.copy()
        if self.box is not None:
            o.box = self.box.copy(o)
        o.vao = self.vao
        o.color = self.color
        return o

    def as_unit(self):
        raise NotImplementedError()
        v = np.array(self.vertices) @ np.array(self.t.abs().rot.matrix33).T + self.t.abs().pos
        n = np.array(self.normals) @ np.array(self.t.abs().rot.matrix33).T
        return v, n

    def __rrshift__(self, obj):
        if len(obj) != 3:
            raise Exception(f"add part faild, {obj}")
        else:
            v, n, vt = obj
            assert len(v) == len(n)
            self.vertices.extend(v)
            self.vertices_t.extend(vt)
            self.normals.extend(n)

    def m(self):
        t = self.t.abs()
        return ( Matrix44.from_translation(t.pos) * t.rot.matrix44.T).astype("f4")


    def get_vao(self, ctx):
        tex = ctx.texture(self.tex.get_size(), 4)
        tex.write(self.tex.get_view("1"))
        self.tex = tex
        
        vertices = np.array(self.vertices, dtype="f4")
        normals = np.array(self.normals, dtype="f4")
        vertices_t = np.array(self.vertices_t, dtype="f4")
        # ~ print("::", vertices.shape)
        # ~ print("::", normals.shape)
        a, b, c = vertices[:,0], vertices[:,1], vertices[:,2]
        d, e, f = normals[:,0], normals[:,1], normals[:,2]
        g, h = vertices_t[:,0], vertices_t[:,1]
        x = np.dstack([a, b, c, d, e, f, g, h])
        vbo = ctx.buffer(x.astype("f4"))
        return ctx.simple_vertex_array(self.prog, vbo, 'aPos', 'aNormal', "aPos_t")

    def draw(self, camera):
        if self.vao is None:
            print("没准备好绘制", self)
            return

        self.prog['modelMat'].write(self.m())
        self.prog['viewMat'].write(camera.v())
        self.prog['projMat'].write(camera.p())
        self.prog['lightPos'].write(camera.pos)
        self.prog['viewPos'].write(camera.pos)
        self.prog['get_normal'].value = 0
        self.tex.use(1)
        self.prog['texture'].value = 1
        self.vao.render(self.mode)

    def draw_normal(self, camera):
        if self.vao is None:
            print("没准备好绘制", self)
            return

        self.prog['modelMat'].write(self.m())
        self.prog['viewMat'].write(camera.v())
        self.prog['projMat'].write(camera.p())
        self.prog['lightPos'].write(camera.pos)
        self.prog['viewPos'].write(camera.pos)
        self.prog['objectColor'].write(self.color)
        self.prog['get_normal'].value = 3
        self.vao.render(self.mode)


    def pile(self, t, anchor:"U(-1~1, -1~1, -1~1)"):
        a = anchor * self.box.size / 2
        t = t.abs()
        self.t.pos = t.pos - t.rot * a
        self.t.rot= t.rot

class Turtle:
    def __init__(self, pos=None, rot=None, parent=None):
        if rot is None:
            self.rot = Quaternion()
        else:
            self.rot = rot
        if pos is None:
            self.pos = V3(0, 0, 0)
        else:
            self.pos = pos
        self.parent = parent


    def copy(self):
        new = Turtle()
        new.rot = self.rot.copy()
        new.pos = self.pos.copy()
        return new

    def post(self, v3):
        t = self.abs()
        x, y, z = v3
        return t.rot * V3(x, y ,z) + t.pos

    def postl(self, v3l):
        t = self.abs()
        l = []
        for x, y, z in v3l:
            l.append(t.rot * V3(x, y ,z) + t.pos)
        return np.array(l)

    def connect(self, he):
        self.parent = he
        pos, rot = self.iadd_inv2(he)
        self.pos = pos
        self.rot = rot

    def abs(self):
        if self.parent is None:
            return self.copy()
        else:
            r = self.parent.abs().iadd(self.pos, self.rot)
            return r

    def disconnect(self):
        t = self.abs()
        self.parent = None
        self.pos = t.pos
        self.rot = t.rot


    def set(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        self.pos = pos
        self.rot = rot

    def walk(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        self.pos += pos
        self.rot = rot * self.rot

    def iwalk(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        self.pos += self.rot * pos
        self.rot = self.rot * rot

    def add(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        return Turtle(self.pos + pos, rot * self.rot)

    def iadd(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        return Turtle(self.pos + self.rot * pos, self.rot * rot)

    def iadd_inv(self, pos, rot):
        rot0 = self.rot * rot.conjugate
        pos0 = self.pos - rot0 * pos
        return Turtle(pos0, rot0)

    def iadd_inv2(self, t0):
        rot = t0.rot.conjugate * self.rot
        pos = t0.rot.conjugate * (self.pos - t0.pos)
        return pos, rot

    @property
    def rt(self):
        return self.rot * U3.rt

    @property
    def up(self):
        return self.rot * U3.up

    @property
    def fd(self):
        return self.rot * U3.fd

    @property
    def lt(self):
        return self.rot * U3.lt

    @property
    def dn(self):
        return self.rot * U3.dn

    @property
    def bk(self):
        return self.rot * U3.bk

    def pile(self, obj, anchor):
        assert isinstance(obj, Obj)
        obj.pile(self, anchor)


class Box:
    # 记录盒子中心的坐标
    # 坐标是相对于宿主的坐标
    def __init__(self, size, master):
        x, y, z = size
        self.size = V3(abs(x), abs(y), abs(z))
        self.pos = U3(0, 0, 0)
        self.rot = Quaternion()
        assert type(master) is Obj
        self.master = master

    def anchor(self, v):
        a = v * self.size / 2
        t = self.master.t.abs().add(self.pos, self.rot)
        # ~ return t.iadd_inv(-a, Quaternion())
        return t.iadd(a)
        # ~ return Turtle(t.pos + self.master.rot * a, t.rot)

    def copy(self, o):
        b = Box(self.size, o)
        b.pos = self.pos
        b.rot = self.rot
        return b

def make_ring(l1, l2, r=False):     # 默认第二个环在第一个环的正方向上
    assert len(l1) * len(l2) != 0
    if len(l1) == len(l2) == 1: assert False
    elif len(l1) == 1:
        a = l1[0]
        if r == False:
            l = []
            for i in range(len(l2)):
                l.extend([a, l2[i], l2[i-1]])
        else:
            l = []
            for i in range(len(l2)):
                l.extend([a, l2[i-1], l2[i]])
        return l
    elif len(l2) == 1:
        return make_ring(l2, l1, r=True)
    else:
        assert len(l1) == len(l2)
        if r == False:
            la = []
            for i in range(len(l2)):
                la.extend([l1[i-1], l2[i], l2[i-1]])
            lb = []
            for i in range(len(l2)):
                lb.extend([l1[i-1], l1[i], l2[i]])
        else:
            la = []
            for i in range(len(l2)):
                la.extend([l1[i-1], l2[i-1], l2[i]])
            lb = []
            for i in range(len(l2)):
                lb.extend([l1[i], l1[i-1], l2[i-1]])
        return la + lb

def make_block(size=U3(0,0,0)):
    t = Turtle()
    x, z, y = size
    x, z, y = abs(x), abs(z), abs(y)
    hx, hz, hy = size / 2
    hx, hz, hy = abs(hx), abs(hz), abs(hy)
    l = np.array([U3(hx, hy, 0), U3(-hx, hy, 0), U3(-hx, -hy, 0), U3(hx, -hy, 0)])
    a, b, c, d = l1 = l - U3(0, 0, hz)
    e, f, g, h = l2 = l + U3(0, 0, hz)
    v = []
    v.extend([a, b, f])
    v.extend([b, c, g])
    v.extend([c, d, h])
    v.extend([d, a, e])
    v.extend([a, f, e])
    v.extend([b, g, f])
    v.extend([c, h, g])
    v.extend([d, e, h])
    v.extend([a, c, b])
    v.extend([a, d, c])
    v.extend([e, f, g])
    v.extend([e, g, h])
    o = Obj()
    o.vertices = v
    o.normals = easy_norm(v)
    o.box = Box(U3(x, y, z), o)
    return o

def make_tri(p1, p2 , p3):
    v = []
    v.extend([p1, p2, p3])
    v.extend([p1, p3, p2])
    o = Obj()
    o.vertices = v
    o.normals = easy_norm(v)
    return o

def easy_norm(l):
    assert len(l) % 3 == 0
    l2 = []
    for i in range(len(l))[::3]:
        a,b,c = l[i], l[i+1], l[i+2]
        a = np.array(a)
        b = np.array(b)
        c = np.array(c)
        x = np.cross(b - a, c - b)
        # ~ x = np.cross(c - b, b - a)
        m = np.linalg.norm(x)
        x = x / m
        l2.append(x)
        l2.append(x)
        l2.append(x)
    return l2

def lookat(p, up=None):
    if up is None:
        up = U3(0, 0, 1)
    m = Matrix44.look_at(U3(0, 0, 0), p, up)
    return Quaternion.from_matrix(m)


n = 50
class Tail:
    # ~ l6 = [U3(0.05 * cos(pi * 2 / 6 * i), 0, 0.05 * sin(pi * 2 / 6 * i)) for i in range(6)][::-1]
    l6 = [U3(0.05 * cos(pi * 2 / n * i), 0, 0.05 * sin(pi * 2 / n * i)) for i in range(n)][::-1]
    def __init__(self, p1, p2):
        v = p2 - p1
        self.v = v
        self.t1 = Turtle(p1, lookat(v))
        self.t2 = Turtle(p2, lookat(v))
        v = make_ring(self.t1.postl(self.l6), self.t2.postl(self.l6))
        n = easy_norm(v)
        self.now = [v, n]
        v0 = V3(0, 0, 0)
        self.acc = [[v0, v0, v0], [v0, v0, v0]]

    def push(self, p):
        v = p - self.t2.pos
        self.v = v
        if norm(p - self.t1.pos) > 0.1:
            self.acc[0].extend(self.now[0])
            self.acc[1].extend(self.now[1])
            self.t1 = self.t2
            self.t2 = Turtle(p, lookat(v))
        else:
            self.t2 = Turtle(p, lookat(v))
        v = make_ring(self.t1.postl(self.l6), self.t2.postl(self.l6))
        n = easy_norm(v)
        self.now = [v, n]

def mapf(l):
    def g(f):
        return [f(a) for a in l]
    return g



def circle(type, d, n):
    r = d / 2
    th = pi * 2 / n
    if type == "xy":
       l = [U3(r * cos(th * i), r * sin(th * i), 0) for i in range(n)]
    elif type == "xz":
        l = [U3(r * cos(th * i), 0, r * sin(th * i)) for i in range(n)]
    elif type == "yz":
        l = [U3(0, r * cos(th * i), r * sin(th * i)) for i in range(n)]
    elif type == "-xy":
       l = [U3(-r * cos(th * i), r * sin(th * i), 0) for i in range(n)]
    elif type == "-xz":
        l = [U3(-r * cos(th * i), 0, r * sin(th * i)) for i in range(n)]
    elif type == "-yz": #左视图
        l = [U3(0, -r * cos(th * i), r * sin(th * i)) for i in range(n)]
    else:
        assert False
    return l

def make_circle(type, d, n):
    c = circle(type, d, n)
    l = []
    l = make_ring([U3(0, 0, 0)], c) + make_ring(c, [U3(0, 0, 0)])
    o = Obj()
    o.vertices = l
    o.normals = easy_norm(l)
    return o

def make_quad(type, a, b):
    c = rect(type, a, b)
    l = []
    l.extend([c[0], c[1], c[2]])
    l.extend([c[0], c[2], c[3]])
    l.extend([c[0], c[2], c[1]])
    l.extend([c[0], c[3], c[2]])
    o = Obj()
    o.vertices = l
    o.normals = easy_norm(l)
    return o

def rect(type, a, b):
    a = a / 2
    b = b / 2
    if type == "xy":
        l = [U3(a, b, 0), U3(-a, b, 0), U3(-a, -b, 0), U3(a, -b, 0)]
    elif type == "xz":
        l = [U3(a, 0, b), U3(-a, 0, b), U3(-a, 0, -b), U3(a, 0, -b)]
    elif type == "yz":
        l = [U3(0, a, b), U3(0, -a, b), U3(0, -a, -b), U3(0, a, -b)]
    elif type == "-xy":
        l = [U3(-a, b, 0), U3(a, b, 0), U3(a, -b, 0), U3(-a, -b, 0)]
    elif type == "-xz":
        l = [U3(-a, 0, b), U3(a, 0, b), U3(a, 0, -b), U3(-a, 0, -b)]
    elif type == "-yz":     # 左视图
        l = [U3(0, -a, b), U3(0, a, b), U3(0, a, -b), U3(0, -a, -b)]
    else:
        assert False
    return np.array(l)


class EasyFbo:
    def __init__(self, size, ctx, quality=False):
        self.ctx  = ctx
        self.size = size
        if quality == False:
            self.texture = self.ctx.texture(self.size, 4)
        else:
            self.texture = self.ctx.texture(self.size, 4, dtype="f4")
        self.depth_texture = self.ctx.depth_texture(self.size)
        self.fbo = self.ctx.framebuffer( self.texture, self.depth_texture)

    def use(self):
        self.fbo.use()

def create_screen_quad(ctx):
    p1 = V3(-1, 1, 0)
    p2 = V3(-1, -1, 0)
    p3 = V3(1, -1, 0)
    p4 = V3(1, 1, 0)
    quad_vertices = np.array([p1, p2, p3, p1, p3, p4], dtype=np.float32)
    uv1 = [0.0, 0.0]
    uv2 = [0.0, 1.0]
    uv3 = [1.0, 1.0]
    uv4 = [1.0, 0.0]
    quad_uvs = np.array([uv1, uv2, uv3, uv1, uv3, uv4, ], dtype=np.float32)
    a, b, c = quad_vertices[:,0], quad_vertices[:,1], quad_vertices[:,2]
    g, h = quad_uvs[:,0], quad_uvs[:,1]
    x = np.dstack([a, b, c, g, h])
    quad_vbo = ctx.buffer(x.astype("f4"))
    return quad_vbo

class Pass:
    def __init__(self, size, ctx, prog, input_fbo,  layer=1):
        self.size = size
        self.layer = layer
        self.ctx  =ctx
        self.input_fbo = input_fbo
        self.prog = prog

        quad_vbo = create_screen_quad(self.ctx)

        self.quad_vao = self.ctx.simple_vertex_array(self.prog, quad_vbo, 'in_position', 'in_texcoord_0')
        camera = Camera(U3(0, self.layer-10, 0))
        camera.proj = Matrix44.orthogonal_projection(-1, 1, -1, 1, 0, self.size[0])
        self.prog["all_mat"].write(camera.p() * camera.v())
        # ~ self.prog['uResolution'].write(np.array(self.size, dtype="f4"))

    def render(self):
        self.prog['input_texture'].value = 2
        self.input_fbo.texture.use(2)
        self.quad_vao.render(moderngl.TRIANGLES)

class PassJust:     # just from texture
    def __init__(self, size, ctx, prog, input_texture,  layer=1):
        self.size = size
        self.layer = layer
        self.ctx  =ctx
        self.input_texture = input_texture
        self.prog = prog

        quad_vbo = create_screen_quad(self.ctx)

        self.quad_vao = self.ctx.simple_vertex_array(self.prog, quad_vbo, 'in_position', 'in_texcoord_0')
        camera = Camera(U3(0,self.layer-10, 0))
        camera.proj = Matrix44.orthogonal_projection(-1, 1, -1, 1, 0, self.size[0])
        self.prog["all_mat"].write(camera.p() * camera.v())

    def init_shader(self, f):
        f(self)

    def render(self):
        self.prog['input_texture'].value = 2
        self.input_texture.use(2)
        self.quad_vao.render(moderngl.TRIANGLES)

class PassCons:
    def __init__(self, size, ctx, prog, input_fbo1, input_fbo2,  layer=1):
        self.size = size
        self.layer = layer
        self.ctx  =ctx
        self.input_fbo1 = input_fbo1
        self.input_fbo2 = input_fbo2
        self.prog = prog

        quad_vbo = create_screen_quad(self.ctx)

        self.quad_vao = self.ctx.simple_vertex_array(self.prog, quad_vbo, 'in_position', 'in_texcoord_0')
        camera = Camera(U3(0, self.layer-10, 0))
        camera.proj = Matrix44.orthogonal_projection(-1, 1, -1, 1, 0, self.size[0])
        self.prog["all_mat"].write(camera.p() * camera.v())
        # ~ self.prog['uResolution'].write(np.array(self.size, dtype="f4"))

    def init_shader(self, f):
        f(self)

    def render(self):
        self.input_fbo1.texture.use(3)
        self.input_fbo2.texture.use(4)
        self.prog['input_texture1'].value = 3
        self.prog['input_texture2'].value = 4
        self.quad_vao.render(moderngl.TRIANGLES)


